package org.fatewa.engine.magic;

import jakarta.servlet.http.HttpServletRequest;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.SneakyThrows;
import lombok.experimental.Accessors;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.BinaryExpression;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.reflection.MetaObject;
import org.fatewa.engine.design.magic.CONNECTOR;
import org.fatewa.engine.design.magic.Subject;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author fatewa
 */
public abstract class AbstractSubjectInterceptor<E, T extends Subject<E>> {

    @Data
    @Accessors(chain = true, fluent = true)
    public static class ActionScope {
        /**
         * 作用域的类型
         */
        private Class<?> clazz;
        /**
         * 作用域的字段名称
         */
        private String field;
        /**
         * 默认使用 and 连接符
         */
        private CONNECTOR connector = CONNECTOR.AND;
    }

    @EqualsAndHashCode(callSuper = true)
    @Data
    @Accessors(chain = true, fluent = true)
    public static class ActionScopeSession extends ActionScope {
        /**
         * 原始 sql
         */
        private String rawSql;
        private MetaObject meta;

        public void sql(String sql) {
            meta().setValue("delegate.boundSql.sql", sql);
        }
    }

    public interface ScopeConfiguration {
        /**
         * 添加一个作用域
         *
         * @param scope 作用域
         * @return 当前
         */
        ScopeConfiguration scope(ActionScope scope);

        /**
         * 获取配置的内容
         *
         * @return 抽取的配置的内容
         */
        List<ActionScope> extract();
    }


    /**
     * 获取所有持有的主体
     *
     * @return 所有持有的主体
     */
    public abstract List<T> subjects();


    /**
     * 获取当前的请求，可用于辅助获取主体 {@link Subject} 持有情况
     *
     * @return 当前的请求
     */
    public HttpServletRequest currentRequest() {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (null != attributes) {
            return attributes.getRequest();
        }
        throw new IllegalStateException("Request not found");
    }

    /**
     * 是否跳过本次处理
     *
     * @return 是否跳过本次处理
     */
    protected boolean skip() {
        return false;
    }


    /**
     * 实例
     */
    private static class SampleScopeConfiguration implements ScopeConfiguration {
        private final List<ActionScope> actions = new ArrayList<>();

        @Override
        public ScopeConfiguration scope(ActionScope scope) {
            actions.add(scope);
            return this;
        }

        @Override
        public List<ActionScope> extract() {
            return actions;
        }
    }


    /**
     * 用户进行作用域配置
     *
     * @param configuration 配置
     */
    protected abstract void configure(ScopeConfiguration configuration);


    /**
     * 抽取所有的作用域配置
     * FIXME 该函数可以增加缓存，加快效率
     *
     * @return 所有的作用域配置
     */
    List<ActionScope> actionScopes() {
        SampleScopeConfiguration configuration = new SampleScopeConfiguration();
        this.configure(configuration);
        return configuration.extract();
    }


    /**
     * 获取 sql 在复杂场景下，用于可自定义进行实现
     *
     * @return 构建完毕的 sql
     */
    protected String getSql(ActionScopeSession session, List<? extends Subject<E>> subjects, MappedStatement mappedStatement) {
        SqlCommandType type = mappedStatement.getSqlCommandType();
        return this.refactor(type, session, subjects);
    }

    /**
     * 重构 sql
     * TODO 实现 删除、更新类型的 SQL 拦截
     *
     * @param type     sql 类型
     * @param session  当前的作用域会话
     * @param subjects 当前持有的主体
     * @return 重构完毕的sql
     */
    private String refactor(SqlCommandType type, ActionScopeSession session, List<? extends Subject<E>> subjects) {
        switch (type) {
            case SELECT:
                return this.querySql(session, subjects);
            default:
                return session.rawSql();
        }
    }

    @SneakyThrows
    private String querySql(ActionScopeSession session, List<? extends Subject<E>> subjects) {
        String sql = session.rawSql();

        final Select select = (Select) CCJSqlParserUtil.parse(sql);
        PlainSelect plainSelect = (PlainSelect) select.getSelectBody();
        final Expression where = plainSelect.getWhere();
        // where 子句不存在
        plainSelect.setWhere(buildWhereClause(subjects, session, where));
        // 构建好的新 sql
        return plainSelect.toString();
    }

    /**
     * 构建 where 从句
     *
     * @param subjects 主体
     * @param session  作用域会话
     * @return where 从句
     */
    private BinaryExpression buildWhereClause(List<? extends Subject<E>> subjects, ActionScopeSession session, Expression where) throws JSQLParserException {
        List<String> scopes = subjects
                .stream().map(it -> String.format("'%s'", it.scope())).collect(Collectors.toList());
        // 格式化后的主体字段
        String formattedScopes = String.format("%s in (%s)", session.field(), String.join(",", scopes));

        final Expression expression = CCJSqlParserUtil.parseCondExpression(formattedScopes);

        switch (session.connector()) {
            case AND:
                return new AndExpression(where, expression);
            case OR:
                return new OrExpression(where, expression);
            default:
                return null;
        }
    }

    public static void registerWhiteClass(Class<?> clazz) {
        RayquazaSqlInterceptor.WHITELIST.add(clazz.getName());
    }

}
